#ifndef CSUPBR_TRANSFORM_H
#define CSUPBR_TRANSFORM_H

#include "geometry.h"
#include <memory>

namespace csupbr {

	struct Transform {
		// transform & inversed matrix
		mutable Mat4 transMat, intransMat;

		Transform() = default;

		Transform(const Mat4& trans)
			: transMat(trans), intransMat(trans.inverse())
		{}

		Transform(const Mat4& trans, const Mat4& intrans)
			: transMat(trans), intransMat(intrans)
		{}

		bool hasScale() const {
			float ax = (this->transMat * Vec4f(1.0f, 0.0f, 0.0f, 1.0f)).lengthSquared();
			float ay = (this->transMat * Vec4f(0.0f, 1.0f, 0.0f, 1.0f)).lengthSquared();
			float az = (this->transMat * Vec4f(0.0f, 0.0f, 1.0f, 1.0f)).lengthSquared();
#define NOT_ONE(x) ((x) < .999f || (x) > 1.001f)
			return NOT_ONE(ax) || NOT_ONE(ay) || NOT_ONE(az);
#undef NOT_ONE
		}

		bool swapHandedness() const {
			// determinant of left-top 3x3 matrix
			float det = (
					(transMat[0][0] * (transMat[1][1] * transMat[2][2] - transMat[1][2] * transMat[2][1])) -
					(transMat[0][1] * (transMat[1][0] * transMat[2][2] - transMat[1][2] * transMat[2][0])) +
					(transMat[0][2] * (transMat[1][0] * transMat[2][1] - transMat[1][1] * transMat[2][0]))
				);
			return det < 0.f;
		}

		inline Point3f operator()(const Point3f& v) const {
			auto re = transMat * Vec4f(v.x, v.y, v.z, 1.0f);
			if (re.w == 1.)
				return Point3f(re.x, re.y, re.z);
			return Point3f(re.x, re.y, re.z) / re.w;
		}

		inline Vec3f operator()(const Vec3f& v) const {
			float x = v.x, y = v.y, z = v.z;
			return {
				transMat[0][0] * x + transMat[0][1] * y + transMat[0][2] * z,
				transMat[1][0] * x + transMat[1][1] * y + transMat[1][2] * z,
				transMat[2][0] * x + transMat[2][1] * y + transMat[2][2] * z,
			};
		}

		inline Normal operator()(const Normal& nor) const {
			float x = nor.x, y = nor.y, z = nor.z;
			return {
				intransMat[0][0] * x + intransMat[1][0] * y + intransMat[2][0] * z,
				intransMat[0][1] * x + intransMat[1][1] * y + intransMat[2][1] * z,
				intransMat[0][2] * x + intransMat[1][2] * y + intransMat[2][2] * z,
			};
		}

		Ray operator()(const Ray& ray) const {
			Ray reRay = ray;
			reRay.origin = (*this)(reRay.origin);
			reRay.direct = (*this)(reRay.direct);
			return reRay;
		}

		void operator()(const Ray& rayin, Ray& rayout) const {
			rayout.origin = (*this)(rayin.origin);
			rayout.direct = (*this)(rayin.direct);
		}

		BBox3f operator()(const BBox3f& b) const {
			const auto& M = *this;
			BBox3f ret((M(b.pMin)));
			ret = BBox3f::Union(ret, M(Point3f(b.pMax.x, b.pMin.y, b.pMin.z)));
			ret = BBox3f::Union(ret, M(Point3f(b.pMin.x, b.pMax.y, b.pMin.z)));
			ret = BBox3f::Union(ret, M(Point3f(b.pMin.x, b.pMin.y, b.pMax.z)));
			ret = BBox3f::Union(ret, M(Point3f(b.pMin.x, b.pMax.y, b.pMax.z)));
			ret = BBox3f::Union(ret, M(Point3f(b.pMax.x, b.pMax.y, b.pMin.z)));
			ret = BBox3f::Union(ret, M(Point3f(b.pMax.x, b.pMin.y, b.pMax.z)));
			ret = BBox3f::Union(ret, M(Point3f(b.pMax.x, b.pMax.y, b.pMax.z)));
			return ret;
		}

		Transform operator()(const Transform& trans) const {
			return Transform(this->transMat * trans.transMat, trans.intransMat * this->intransMat);
		}

		Transform inverse()const {
			return Transform(this->intransMat, this->transMat);
		}

		std::shared_ptr<Transform> inverseRef() const {
			return std::make_shared<Transform>(this->intransMat, this->transMat);
		}

		Transform operator*(const Transform& b) {
			return Transform(this->transMat * b.transMat);
		}

		////////////////////////
		/// Creater
		////////////////////////

		static std::shared_ptr<Transform> Create(const Vec3f& position, const Vec3f& scale) {
			Mat4 trans = Mat4::translate(position) * Mat4::scale(scale);
			return std::make_shared<Transform>(trans);
		}

		static std::shared_ptr<Transform> Create(const Vec3f& position, const Vec3f& scale,
			const Vec3f& axis, const float angle) {
			Mat4 trans = Mat4::translate(position) * Mat4::rotate(axis.normalized(), angle) * Mat4::scale(scale);
			return std::make_shared<Transform>(trans);
		}

	};

}

#endif // !CSUPBR_TRANSFORM_H